package com.travel.core;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.travel.util.Constants;

/**
 * DAO类工厂选择器，采用反射机制，目前只匹配com.travel.dao包中的接口，及实现类在com.travel.dao.impl包中
 *
 * @version 1.0
 * @author SAGITTAR
 */
public final class DaoFactory {

    private static final Logger LOGGER = LogManager.getLogger(DaoFactory.class);

    /**
     * 接口实现映射集，K为接口，V是对应该接口的所有实现类的实例
     */
    private static final Map<Class<?>, List<Object>> INSTANCE_MAP = new HashMap<>();

    private DaoFactory() {
    }

    static {
        // 获取绝对路径
        String classpath = null;
        try {
            // 获取源代码的根目录，绝对路径
            classpath = URLDecoder.decode(DaoFactory.class.getResource("/").getFile(), Constants.UTF_8);
            // 为了兼容单元测试的适配
            String testCase = "test-";
            if (StringUtils.contains(classpath, testCase)) {
                LOGGER.debug("已完成测试环境的路径转换");
                classpath = classpath.replace(testCase, StringUtils.EMPTY);
            }
            LOGGER.debug("当前运行环境的根路径为{}", classpath);
        } catch (UnsupportedEncodingException e) {
            LOGGER.error(e.getLocalizedMessage(), e);
        }
        // 扫描基础包，接口包和实现类的包
        String interfacePath = "com/travel/dao";
        String implPath = "impl";
        String fileSuffix = ".class";
        File rootInterfacePath = new File(classpath, interfacePath);
        // 遍历接口和实现类，只加载文件
        File[] interfaceFiles = rootInterfacePath.listFiles((file) -> file.isFile());
        File[] implFiles = new File(rootInterfacePath, implPath).listFiles((file) -> file.isFile());

        // 有多少接口和实现类
        int interfaceCount = 0;
        int implCount = 0;
        if (Objects.nonNull(interfaceFiles) && Objects.nonNull(implFiles)) {
            ClassLoader classLoader = DaoFactory.class.getClassLoader();
            // 所有实现类
            List<Class<?>> implClassList = new ArrayList<>(implFiles.length);
            for (File implFile : Arrays.asList(implFiles)) {
                try {
                    // 文件名剔除.class后缀
                    String name = implFile.getName().substring(0, implFile.getName().lastIndexOf(fileSuffix));
                    // 替换为包名格式
                    String implQualifier = Paths.get(interfacePath, implPath, name).toString().replace("\\", ".");
                    implClassList.add(classLoader.loadClass(implQualifier));
                } catch (ClassNotFoundException e) {
                    LOGGER.error(e.getLocalizedMessage(), e);
                }
            }
            implCount = implClassList.size();

            for (File current : interfaceFiles) {
                try {
                    String name = current.getName().substring(0, current.getName().lastIndexOf(fileSuffix));
                    // 替换为包名格式
                    String interfaceQualifier = Paths.get(interfacePath, name).toString().replace("\\", ".");
                    Class<?> currentInterface = classLoader.loadClass(interfaceQualifier);
                    // 只加载接口
                    if (currentInterface.isInterface()) {
                        interfaceCount++;
                        // 遍历所有实现类，加载一个接口存在多个实现类的情况
                        List<Object> correspondingImplList = new ArrayList<>();
                        List<String> implNameList = new ArrayList<>(implFiles.length);
                        for (Class<?> implClass : implClassList) {
                            if (currentInterface.isAssignableFrom(implClass)) {
                                correspondingImplList.add(implClass.newInstance());
                                implNameList.add(implClass.getTypeName());
                            }
                        }
                        INSTANCE_MAP.put(currentInterface, correspondingImplList);
                        LOGGER.debug("扫描接口为{}，实现类有{}", currentInterface.getName(), implNameList);
                    }
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                    LOGGER.error(e.getLocalizedMessage(), e);
                }
            }
        }
        LOGGER.debug("本次初始化共加载了{}个接口，{}个实现类", interfaceCount, implCount);
    }

    /**
     * 获取对应的数据访问对象，并且只有一个实现类，若有多个则返回第一个实现类的实例
     *
     * @param cls 对应的接口类型
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <DAO> DAO getDao(Class<? extends DAO> cls) {
        DAO baseDao = null;
        // 如果传入实现类的类型，则执行
        if (!cls.isInterface()) {
            // 从所有实现类找到匹配的一个
            Collection<List<Object>> implListCollection = INSTANCE_MAP.values();
            if (Objects.nonNull(implListCollection)) {
                outer: for (List<Object> implList : implListCollection) {
                    for (Object obj : implList) {
                        if (obj.getClass() == cls) {
                            baseDao = (DAO) obj;
                            break outer;
                        }
                    }
                }
            }
        } else {
            List<Object> implList = INSTANCE_MAP.get(cls);
            if (Objects.nonNull(implList) && !implList.isEmpty()) {
                baseDao = (DAO) implList.get(0);
            }
        }
        Objects.requireNonNull(baseDao);
        LOGGER.debug("需要获取{}的实例，适配结果为{}", cls.getTypeName(), baseDao.getClass().getTypeName());
        return baseDao;
    }
}
